package com.xyy.saas.payment.service.reconciliation.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.xyy.saas.payment.cores.enums.FbankTradeCodeEnum;
import com.xyy.saas.payment.dao.mapper.BankBillJd0028Mapper;
import com.xyy.saas.payment.dao.mapper.PayResultOrderMapper;
import com.xyy.saas.payment.dao.mapper.RefundOrderMapper;
import com.xyy.saas.payment.dao.mapper.TradeErrorRecordsMapper;
import com.xyy.saas.payment.dao.model.BankBillJd0028;
import com.xyy.saas.payment.dao.model.PayResultOrder;
import com.xyy.saas.payment.dao.model.RefundOrder;
import com.xyy.saas.payment.dao.model.TradeErrorRecords;
import com.xyy.saas.payment.dto.ReconcileTradeDto;
import com.xyy.saas.payment.enums.ReconcileTradeErrorDataTypeEnum;
import com.xyy.saas.payment.enums.ReconcileTradeErrorProcessStatus;
import com.xyy.saas.payment.enums.ReconcileTradeErrorTypeEnum;
import com.xyy.saas.payment.enums.ReconciliationType;
import com.xyy.saas.payment.exception.PaymentException;
import com.xyy.saas.payment.manage.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.pojo.dto.ReconcileContext;
import com.xyy.saas.payment.trace.AlarmService;
import com.xyy.saas.payment.trace.DingTalkMessage;
import com.xyy.saas.payment.util.AmountCastUtil;
import com.xyy.saas.payment.util.DateUtil;
import com.xyy.saas.payment.util.ListUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author wangzeqi
 */
@Slf4j
@Service
public class JdTradeCheckService {

    @Resource
    private PayResultOrderMapper payResultOrderMapper;
    @Resource
    private RefundOrderMapper refundOrderMapper;
    @Resource
    private TradeErrorRecordsMapper tradeErrorRecordsMapper;
    @Resource
    private AlarmService alarmService;
    @Resource
    private BankBillJd0028Mapper bankBillJd0028Mapper;


    public void reconcileTradeByType(ReconcileContext context) {
        String logPrefix = "jdReconcileTradeDetails - ";
        checkReconcileTradeParamDto(logPrefix, context);

        // jd单边
        try {
            long beginTime = System.currentTimeMillis();
            reconcileJdSingleSideTrade(logPrefix, context);
            log.info("{} reconcileJdSingleSideTrade end, cost {}ms", logPrefix, System.currentTimeMillis() - beginTime);
        } catch (Exception e) {
            log.error("{} reconcileJdSingleSideTrade error, context: {}", logPrefix, context, e);
        }

        // payment单边
        try {
            long beginTime = System.currentTimeMillis();
            reconcilePaymentSingleSideTrade(logPrefix, context);
            log.info("{} reconcilePaymentSingleSideTrade end, cost {}ms", logPrefix, System.currentTimeMillis() - beginTime);
        } catch (Exception e) {
            log.error("{} reconcilePaymentSingleSideTrade error, context: {}", logPrefix, context, e);
        }

        // 错账
        try {
            long beginTime = System.currentTimeMillis();
            reconcileJdPaymentErrorAmountTrade(logPrefix, context);
            log.info("{} reconcileJdPaymentErrorAmountTrade end, cost {}ms", logPrefix, System.currentTimeMillis() - beginTime);
        } catch (Exception e) {
            log.error("{} reconcileJdPaymentErrorAmountTrade error, context: {}", logPrefix, context, e);
        }

    }

    private void reconcileJdPaymentErrorAmountTrade(String logPrefix, ReconcileContext context) {
        log.info("{} reconcileJdPaymentErrorAmountTrade start, context: {}", logPrefix, context);

        // 支付
        List<ReconcileTradeDto> payTradeErrorAmountRecords = bankBillJd0028Mapper.findJdChannePayTradeErrorAmount(context.getBeginTime(), context.getEndTime());
        // 退款
        List<ReconcileTradeDto> refundTradeErrorAmountRecords = bankBillJd0028Mapper.findJdChanneRefundTradeErrorAmount(context.getBeginTime(), context.getEndTime());

        List<ReconcileTradeDto> allTradeErrorAmountRecords = ListUtils.mergeLists(payTradeErrorAmountRecords, refundTradeErrorAmountRecords);
        if (CollectionUtil.isEmpty(allTradeErrorAmountRecords)) {
            return;
        }

        List<TradeErrorRecords> tradeErrorRecords = new ArrayList<>();
        fillTradeErrorRecordsByTradeErrorAmountRecords(tradeErrorRecords, allTradeErrorAmountRecords);
        persistPaymentJdTradeErrorAmountRecords(logPrefix, context, tradeErrorRecords);
    }

    private void fillTradeErrorRecordsByTradeErrorAmountRecords(List<TradeErrorRecords> tradeErrorRecords, List<ReconcileTradeDto> allTradeErrorAmountRecords) {
        for (ReconcileTradeDto fromDto : allTradeErrorAmountRecords) {
            TradeErrorRecords toDto = new TradeErrorRecords()
                    .setBizNo(fromDto.getBizNo())
                    .setDataType(ReconcileTradeErrorDataTypeEnum.TRADE_ERROR.getCode())
                    .setErrorType(ReconcileTradeErrorTypeEnum.ERROR_ACCOUNT.getCode())
                    .setErrorDescription(String.join("|",
                            "京东渠道交易错账记录",
                            fromDto.getBizNo()))
                    .setItemCode(ReconciliationType.JD_TRADE_DETAIL.name())
                    .setTradeCode(FbankTradeCodeEnum.FTC_30.getCode())
                    //.setBizCode()
                    .setProcessStatus(ReconcileTradeErrorProcessStatus.PENDING.getCode())
                    .setExpectedAmount(AmountCastUtil.castFen2YuanByBigDecimal(fromDto.getExpectedAmount()))
                    .setActualAmount(AmountCastUtil.castFen2YuanByBigDecimal(fromDto.getActualAmount()))
                    .setTradeTime(fromDto.getTradeTime())
                    .setReconcileDate(new Date());
            tradeErrorRecords.add(toDto);
        }
    }

    private void persistPaymentJdTradeErrorAmountRecords(String logPrefix, ReconcileContext context, List<TradeErrorRecords> tradeErrorRecords) {
        if (CollectionUtil.isEmpty(tradeErrorRecords)) {
            return;
        }

        log.warn("{} persistPaymentJdTradeErrorAmountRecords errorRecords size:{}, context:{}", logPrefix, tradeErrorRecords.size(), JSON.toJSONString(context));
        alarmService.alarm(DingTalkMessage.builder()
                .type("京东交易明细对账")
                .method("persistPaymentJdTradeErrorAmountRecords")
                .msg("京东渠道交易金额不一致记录，共计" + tradeErrorRecords.size() + "条")
                .build());

        int maxLimit = 200;
        for (List<TradeErrorRecords> partRecords : Lists.partition(tradeErrorRecords, maxLimit)) {
            tradeErrorRecordsMapper.batchInsert(partRecords);
        }
    }

    private void reconcilePaymentSingleSideTrade(String logPrefix, ReconcileContext context) {
        log.info("{} reconcilePaymentSingleSideTrade start, context: {}", logPrefix, context);
        // 支付
        List<PayResultOrder> jdPaySingleSideList = payResultOrderMapper.findJdChannelSingleSideTrade(context.getBeginTime(), context.getEndTime());
        // 退款
        List<RefundOrder> jdRefundSingleSideList = refundOrderMapper.findJdChannelSingleSideTrade(context.getBeginTime(), context.getEndTime());
        // 存差异记录
        List<TradeErrorRecords> tradeErrorRecords = new ArrayList<>();
        fillTradeErrorRecordsByPaymentSingleSideRecords(tradeErrorRecords, jdPaySingleSideList, jdRefundSingleSideList);
        persistPaymentSingleSideReconcileTradeRecords(logPrefix, context, tradeErrorRecords);
    }

    private void fillTradeErrorRecordsByPaymentSingleSideRecords(List<TradeErrorRecords> tradeErrorRecords, List<PayResultOrder> jdPaySingleSideList,
                                                                 List<RefundOrder> jdRefundSingleSideList) {
        if (CollectionUtil.isNotEmpty(jdPaySingleSideList)) {
            for (PayResultOrder payResultOrder : jdPaySingleSideList) {
                TradeErrorRecords toDto = new TradeErrorRecords()
                        .setBizNo(payResultOrder.getPayNo())
                        .setDataType(ReconcileTradeErrorDataTypeEnum.TRADE_ERROR.getCode())
                        .setErrorType(ReconcileTradeErrorTypeEnum.SINGLE_SIDE.getCode())
                        .setErrorDescription(String.join("|",
                                "京东渠道交易支付平台支付单边记录",
                                payResultOrder.getPayNo()))
                        .setItemCode(ReconciliationType.JD_TRADE_DETAIL.name())
                        .setTradeCode(FbankTradeCodeEnum.FTC_30.getCode())
                        //.setBizCode()
                        .setProcessStatus(ReconcileTradeErrorProcessStatus.PENDING.getCode())
                        .setExpectedAmount(AmountCastUtil.castFen2YuanByBigDecimal(payResultOrder.getAmount()))
                        .setActualAmount(BigDecimal.ZERO)
                        .setTradeTime(payResultOrder.getCtime())
                        .setReconcileDate(new Date());
                tradeErrorRecords.add(toDto);
            }
        }

        if (CollectionUtil.isNotEmpty(jdRefundSingleSideList)) {
            for (RefundOrder refundOrder : jdRefundSingleSideList) {
                TradeErrorRecords toDto = new TradeErrorRecords()
                        .setBizNo(refundOrder.getRefundNo())
                        .setDataType(ReconcileTradeErrorDataTypeEnum.TRADE_ERROR.getCode())
                        .setErrorType(ReconcileTradeErrorTypeEnum.SINGLE_SIDE.getCode())
                        .setErrorDescription(String.join("|",
                                "京东渠道交易支付平台退款单边记录",
                                refundOrder.getRefundNo()))
                        .setItemCode(ReconciliationType.JD_TRADE_DETAIL.name())
                        .setTradeCode(FbankTradeCodeEnum.FTC_30.getCode())
                        //.setBizCode()
                        .setProcessStatus(ReconcileTradeErrorProcessStatus.PENDING.getCode())
                        .setExpectedAmount(AmountCastUtil.castFen2YuanByBigDecimal(refundOrder.getAmount()))
                        .setActualAmount(BigDecimal.ZERO)
                        .setTradeTime(refundOrder.getSuccessTime())
                        .setReconcileDate(new Date());
                tradeErrorRecords.add(toDto);
            }
        }
    }

    // 支付平台单边
    private void persistPaymentSingleSideReconcileTradeRecords(String logPrefix, ReconcileContext context, List<TradeErrorRecords> tradeErrorRecords) {
        if (CollectionUtil.isEmpty(tradeErrorRecords)) {
            return;
        }

        log.warn("{} persistPaymentSingleSideReconcileTradeRecords errorRecords size:{}, context:{}", logPrefix, tradeErrorRecords.size(), JSON.toJSONString(context));
        alarmService.alarm(DingTalkMessage.builder()
                .type("京东交易明细对账")
                .method("persistPaymentSingleSideReconcileTradeRecords")
                .msg("京东渠道支付平台单边记录，共计" + tradeErrorRecords.size() + "条")
                .build());

        int maxLimit = 200;
        for (List<TradeErrorRecords> partRecords : Lists.partition(tradeErrorRecords, maxLimit)) {
            tradeErrorRecordsMapper.batchInsert(partRecords);
        }
    }

    private void reconcileJdSingleSideTrade(String logPrefix, ReconcileContext context) {
        log.info("{} reconcileJdSingleSideTrade start, context: {}", logPrefix, context);

        // 支付
        List<BankBillJd0028> jdPayTradeSingleSideList = bankBillJd0028Mapper.findJdSingleSidePayTrade(context.getBeginTime(), context.getEndTime());
        // 退款
        List<BankBillJd0028> jdRefundTradeSingleSideList = bankBillJd0028Mapper.findJdSingleSideRefundTrade(context.getBeginTime(), context.getEndTime());
        // merge
        List<BankBillJd0028> allSingleSideRecords = ListUtils.mergeLists(jdPayTradeSingleSideList, jdRefundTradeSingleSideList);
        if (CollectionUtil.isEmpty(allSingleSideRecords)) {
            return;
        }

        // 存差异记录
        List<TradeErrorRecords> tradeErrorRecords = new ArrayList<>();
        fillTradeErrorRecordsByJdSingleSideRecords(tradeErrorRecords, allSingleSideRecords);
        persistJdSingleSideReconcileTradeRecords(logPrefix, context, tradeErrorRecords);
    }

    private void fillTradeErrorRecordsByJdSingleSideRecords(List<TradeErrorRecords> tradeErrorRecords, List<BankBillJd0028> allSingleSideRecords) {

        for (BankBillJd0028 fromDto : allSingleSideRecords) {
            TradeErrorRecords toDto = new TradeErrorRecords()
                    .setBizNo(fromDto.getOrderNo())
                    .setDataType(ReconcileTradeErrorDataTypeEnum.SETTLE_ERROR.getCode())
                    .setErrorType(ReconcileTradeErrorTypeEnum.SINGLE_SIDE.getCode())
                    .setErrorDescription(String.join("|",
                            "京东渠道交易京东" + fromDto.getOrderType() + "单边记录",
                            fromDto.getOrderNo()))
                    .setItemCode(ReconciliationType.JD_TRADE_DETAIL.name())
                    .setTradeCode(FbankTradeCodeEnum.FTC_30.getCode())
                    //.setBizCode()
                    .setProcessStatus(ReconcileTradeErrorProcessStatus.PENDING.getCode())
                    .setExpectedAmount(BigDecimal.ZERO)
                    .setActualAmount(fromDto.getOrderAmount())
                    .setTradeTime(DateUtil.sdf1ToDate(fromDto.getDealTime()))
                    .setReconcileDate(new Date());
            tradeErrorRecords.add(toDto);
        }
    }

    // 京东单边
    private void persistJdSingleSideReconcileTradeRecords(String logPrefix, ReconcileContext context, List<TradeErrorRecords> tradeErrorRecords) {
        if (CollectionUtil.isEmpty(tradeErrorRecords)) {
            return;
        }

        log.warn("{} persistJdSingleSideReconcileTradeRecords errorRecords size:{}, context:{}", logPrefix, tradeErrorRecords.size(), JSON.toJSONString(context));
        alarmService.alarm(DingTalkMessage.builder()
                .type("京东交易明细对账")
                .method("persistJdSingleSideReconcileTradeRecords")
                .msg("京东渠道京东单边记录，共计" + tradeErrorRecords.size() + "条")
                .build());

        int maxLimit = 200;
        for (List<TradeErrorRecords> partRecords : Lists.partition(tradeErrorRecords, maxLimit)) {
            tradeErrorRecordsMapper.batchInsert(partRecords);
        }

    }

    @SneakyThrows
    private void checkReconcileTradeParamDto(String logPrefix, ReconcileContext context) {
        if (context == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        LocalDateTime beginTime = context.getBeginTime();
        LocalDateTime endTime = context.getEndTime();
        if (beginTime == null || endTime == null) {
            endTime = LocalDate.now().atStartOfDay();
            beginTime = endTime.minusDays(1);
        }

        if (Math.abs(ChronoUnit.DAYS.between(beginTime, endTime)) > 30) {
            log.error("{} Method checkReconcileTradeParamDto time diff days > 30", logPrefix);
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

    }

}
